﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Globalization;
using System.Linq;
using Microsoft.Practices.ServiceLocation;
using NHibernate;
using NHibernate.Criterion;
using NHibernate.Linq;
using Tanmia.Basis.Properties;

namespace Tanmia.Basis.Data.NHibernate
{
    public class Repository<TCore> : IRepository<TCore>
           where TCore : class
    {
        private ISessionGate sessionGate;
        private DatabaseConfiguration dbConfig;

        public Repository(ISessionManager sessionManager)
        {
            if (sessionManager as ISessionGate == null)
                throw new ArgumentException("sessionManager");

            this.sessionGate = sessionManager as ISessionGate;
            dbConfig = ServiceLocator.Current.GetInstance<DatabaseConfiguration>("AppDb");
        }

        #region IRepository<T> Members

        public TCore Save(TCore entity )
        {
            try
            {
                using (var tx = sessionGate.Session.BeginTransaction())
                {
                    sessionGate.Session.SaveOrUpdate(entity);
                    tx.Commit();
                }

                return entity;
            }
            catch (Exception ex)
            {
                Exception tmpEx = TryCatchConstraintViolationException(ex);
                if (tmpEx != null)
                    throw tmpEx;

                throw ex;
            }
        }

        public TCore Update(TCore entity)
        {
            try
            {
                using (TransactionSpace ts = new TransactionSpace(sessionGate))
                {
                    ts.Session.Update(entity);
                    ts.Commit();
                }

                return entity;
            }
            catch (Exception ex)
            {
                Exception tmpEx = TryCatchConstraintViolationException(ex);
                if (tmpEx != null)
                    throw tmpEx;

                throw ex;
            }
        }

        public void Delete(TCore entity)
        {
            using (var tx = sessionGate.Session.BeginTransaction())
            {
                sessionGate.Session.Delete(entity);
                tx.Commit();
            }
        }

        public TCore Get(int entityID)
        {
            return sessionGate.Session.Get<TCore>(entityID);
        }

        public IList<TCore> Gets()
        {
            ICriteria criteria = sessionGate.Session.CreateCriteria<TCore>();
            return criteria.List<TCore>();
        }

        public IList<TCore> Search(Dictionary<string, object> propertyValuepairs)
        {
            ICriteria criteria = sessionGate.Session.CreateCriteria<TCore>();

            foreach (string key in propertyValuepairs.Keys)
                if (propertyValuepairs[key] != null)
                    criteria.Add(Expression.Eq(key, propertyValuepairs[key]));
                else
                    criteria.Add(Expression.IsNull(key));

            return criteria.List<TCore>();
        }

        public IList<TCore> Gets(Specification<TCore> specs)
        {
            return sessionGate.Session.Query<TCore>().Where(specs.IsSatisfiedBy()).ToList();
        }

        public IList<TCore> Gets(System.Linq.Expressions.Expression<Func<TCore, bool>> satisfy)
        {
            return sessionGate.Session.Query<TCore>().Where(satisfy).ToList();
        }

        public IQueryable<TCore> RepositoryItems
        {
            get { return sessionGate.Session.Query<TCore>(); }
        }

        #endregion

        #region Exception Handling
        private TEx TryGetException<TEx>(Exception ex)
            where TEx : Exception
        {
            Exception tmpEx = ex;
            while (tmpEx != null)
                if (tmpEx is TEx)
                    break;
                else
                    tmpEx = tmpEx.InnerException;

            return tmpEx as TEx;
        }

        private Exception TryCatchConstraintViolationException(Exception ex)
        {
            SqlException sqlEx = TryGetException<SqlException>(ex);
            if (sqlEx != null && sqlEx.Number == 2627)
            {
                string[] parts = sqlEx.Errors[0].Message.Split('\'');
                string constraintName = parts[1];
                string tableName = parts[3];
                string columnName = GetConstraintColumn(constraintName, tableName);
                Type entity = dbConfig.GetEntity(tableName);
                return new ConstraintViolationException(string.Format(CultureInfo.CurrentCulture, Resources.RepositoryConstraintViolation, entity.Name, columnName), ex, constraintName, entity, columnName);
            }

            return null;
        }

        private string GetConstraintColumn(string constraintName, string tableName)
        {
            string columnName = string.Empty;

            using (ISession session = sessionGate.Session)
            {
                IDbCommand cmd = session.Connection.CreateCommand();
                cmd.CommandText = string.Format("DECLARE @indID INT; SELECT @indID = index_id FROM sys.indexes WHERE name = '{0}'; SELECT index_col('{1}', @indID, 1)", constraintName, tableName);
                cmd.CommandType = System.Data.CommandType.Text;
                columnName = cmd.ExecuteScalar().ToString();
            }
            return columnName;
        }

        #endregion
    }
}
